package com.yumo.kangchenjunga.coupon.v1;

import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.yumo.kangchenjunga.activity.Activity;

@Service
public final class CouponListService {

	@Autowired
	private CouponPoolRepository poolRepository;
	@Autowired
	private CouponCustomerAllocationRepository allocationRepository;
	@Autowired
	private CouponCustomerObtainmentRepository obtainmentRepository;
	@Autowired
	private CouponLogRepository logRepository;

	public Page<CouponListItemVo> poolPage(Pageable pageable) {
		var page = poolRepository.findByStatus(CouponStatus.UNALLOCATED, pageable);

		return page.map(this::poolConvertEntityToVo);
	}

	public Page<CouponListItemVo> poolQuery(CouponListItemVo coupon, Pageable pageable) {
		var entity = poolBuildEntityFromVo(coupon);
		var matcher = ExampleMatcher.matchingAll().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING)
				.withIgnorePaths(ignorePaths());
		var example = Example.of(entity, matcher);
		var page = poolRepository.findAll(example, pageable);

		return page.map(this::poolConvertEntityToVo);
	}

	private CouponPoolEntity poolBuildEntityFromVo(CouponListItemVo vo) {
		var entity = new CouponPoolEntity();

		entity.activity = new Activity();
		entity.activity.setId(vo.activityId);
		entity.couponCode = vo.couponCode;
		entity.secretCode = vo.secretCode;
		entity.status = CouponStatus.UNALLOCATED;

		return entity;
	}

	private String[] ignorePaths() {
		return new String[] { "activity.customerLevels", "activity.status", "couponId", "id", "termByMinute" };
	}

	private CouponListItemVo poolConvertEntityToVo(CouponPoolEntity entity) {
		var vo = new CouponListItemVo();

		vo.activityId = entity.activity.getId();
		vo.activityTitle = entity.activity.title;
		vo.couponCode = entity.couponCode;
		vo.couponId = entity.getId();
		vo.createTime = entity.createTime;
		vo.createType = entity.createType;
		vo.poolStatus = entity.status;
		vo.secretCode = entity.secretCode;
		vo.termByMinute = entity.termByMinute;

		return vo;
	}

	public Page<CouponListItemVo> allocationPage(Pageable pageable) {
		var page = allocationRepository.findByStatus(CouponStatus.ALLOCATED, pageable);

		return page.map(this::allocationConvertEntityToVo);
	}

	public Page<CouponListItemVo> allocationQuery(CouponListItemVo coupon, Pageable pageable) {
		var entity = allocationBuildEntityFromVo(coupon);
		var matcher = ExampleMatcher.matchingAll().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING)
				.withIgnorePaths(ignorePaths());
		var example = Example.of(entity, matcher);
		var page = allocationRepository.findAll(example, pageable);

		return page.map(this::allocationConvertEntityToVo);
	}

	private CouponCustomerAllocationEntity allocationBuildEntityFromVo(CouponListItemVo vo) {
		var entity = new CouponCustomerAllocationEntity();

		entity.activity = new Activity();
		entity.activity.setId(vo.activityId);
		entity.couponCode = vo.couponCode;
		entity.secretCode = vo.secretCode;
		entity.status = CouponStatus.ALLOCATED;

		return entity;
	}

	private CouponListItemVo allocationConvertEntityToVo(CouponCustomerAllocationEntity entity) {
		var vo = new CouponListItemVo();

		vo.activityId = entity.activity.getId();
		vo.activityTitle = entity.activity.title;
		vo.allocateTime = entity.allocateTime;
		vo.allocationStatus = entity.status;
		vo.couponCode = entity.couponCode;
		vo.couponId = entity.getId();
		vo.customerId = entity.customer.getId();
		vo.customerLevel = entity.customer.customerLevel;
		vo.customerNickname = entity.customer.nickname;
		vo.customerNumber = entity.customer.customerNumber;
		vo.secretCode = entity.secretCode;
		vo.termByMinute = entity.termByMinute;

		return vo;
	}

	public Page<CouponListItemVo> obtainmentPage(Pageable pageable) {
		var page = obtainmentRepository.findByStatus(CouponStatus.OBTAINED, pageable);

		return page.map(this::obtainmentConvertEntityToVo);
	}

	public Page<CouponListItemVo> obtainmentQuery(CouponListItemVo coupon, Pageable pageable) {
		var entity = obtainmentBuildEntityFromVo(coupon);
		var matcher = ExampleMatcher.matchingAll().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING)
				.withIgnorePaths(ignorePaths());
		var example = Example.of(entity, matcher);
		var page = obtainmentRepository.findAll(example, pageable);

		return page.map(this::obtainmentConvertEntityToVo);
	}

	private CouponCustomerObtainmentEntity obtainmentBuildEntityFromVo(CouponListItemVo vo) {
		var entity = new CouponCustomerObtainmentEntity();

		entity.activity = new Activity();
		entity.activity.setId(vo.activityId);
		entity.couponCode = vo.couponCode;
		entity.secretCode = vo.secretCode;
		entity.status = CouponStatus.OBTAINED;

		return entity;
	}

	private CouponListItemVo obtainmentConvertEntityToVo(CouponCustomerObtainmentEntity entity) {
		var vo = new CouponListItemVo();

		vo.activityId = entity.activity.getId();
		vo.activityTitle = entity.activity.title;
		vo.couponCode = entity.couponCode;
		vo.couponId = entity.getId();
		vo.customerId = entity.customer.getId();
		vo.customerLevel = entity.customer.customerLevel;
		vo.customerNickname = entity.customer.nickname;
		vo.customerNumber = entity.customer.customerNumber;
		vo.effectiveTime = entity.effectiveTime;
		vo.expirationTime = entity.expirationTime;
		vo.obtainStatus = entity.status;
		vo.obtainTime = entity.obtainTime;
		vo.secretCode = entity.secretCode;
		vo.usedTime = entity.usedTime;

		return vo;
	}

	public Page<CouponListItemVo> usedPage(Pageable pageable) {
		var page = obtainmentRepository.findByStatus(CouponStatus.USED, pageable);

		return page.map(this::obtainmentConvertEntityToVo);
	}

	public Page<CouponListItemVo> usedQuery(CouponListItemVo coupon, Pageable pageable) {
		var entity = usedBuildEntityFromVo(coupon);
		var matcher = ExampleMatcher.matchingAll().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING)
				.withIgnorePaths(ignorePaths());
		var example = Example.of(entity, matcher);
		var page = obtainmentRepository.findAll(example, pageable);

		return page.map(this::obtainmentConvertEntityToVo);
	}

	private CouponCustomerObtainmentEntity usedBuildEntityFromVo(CouponListItemVo vo) {
		var entity = obtainmentBuildEntityFromVo(vo);

		entity.status = CouponStatus.USED;

		return entity;
	}

	public Page<CouponListItemVo> usedSearch(Integer activityId, Integer companyId, Integer storeId,
			String couponCode, LocalDate startDate, LocalDate endDate, Pageable pageable) {
		QCouponLogEntity log = QCouponLogEntity.couponLogEntity;
		BooleanExpression exp = log.op.eq("redeem").and(log.opResult.eq("success"));

		if (activityId != null) {
			exp = exp.and(log.activityId.eq(activityId));
		}

		if (companyId != null) {
			exp = exp.and(log.companyId.eq(companyId));
		}

		if (storeId != null && storeId != 0) {
			exp = exp.and(log.storeId.eq(storeId));
		}

		if (couponCode != null) {
			exp = exp.and(log.inputCouponCode.like(String.format("%%%s%%", couponCode)));
		}

		if (startDate != null) {
			exp = exp.and(log.opTime.goe(startDate.atStartOfDay(ZoneOffset.UTC).toInstant()));
		}

		if (endDate != null) {
			exp = exp.and(log.opTime.lt(endDate.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant()));
		}

		return logRepository.findAll(exp, pageable).map(this::redeemConvertEntityToVo);
	}

	private CouponListItemVo redeemConvertEntityToVo(CouponLogEntity entity) {
		var vo = new CouponListItemVo();

		vo.activityId = entity.activityId;
		vo.activityTitle = entity.activityTitle;
		vo.companyId = entity.companyId;
		vo.companyName = entity.companyName;
		vo.couponCode = entity.inputCouponCode;
		vo.couponId = entity.couponId;
		vo.customerId = entity.customerId;
		vo.customerNumber = entity.customerNumber;
		vo.obtainStatus = CouponStatus.USED;
		vo.secretCode = entity.inputSecretCode;
		vo.storeId = entity.storeId;
		vo.storeName = entity.storeName;
		vo.usedTime = entity.opTime;

		return vo;
	}

	public void obtainmentExport(CouponListItemVo coupon, OutputStream out) throws IOException {
		try (Workbook workbook = WorkbookFactory.create(true)) {
			var sheet = workbook.createSheet();
			var result = obtainmentSearchForExport(coupon);

			{
				Row row = sheet.createRow(0);
				Cell cell;

				cell = row.createCell(0);
				cell.setCellValue("序号");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(1);
				cell.setCellValue("权益ID");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(2);
				cell.setCellValue("权益名称");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(3);
				cell.setCellValue("客户号");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(4);
				cell.setCellValue("验证码");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(5);
				cell.setCellValue("密码");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(6);
				cell.setCellValue("领取时间");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(7);
				cell.setCellValue("生效日期");
				cell.setCellType(CellType.STRING);
				cell = row.createCell(8);
				cell.setCellValue("失效日期");
				cell.setCellType(CellType.STRING);
			}

			for (int i = 0; i < result.size(); ++i) {
				Row row = sheet.createRow(i + 1);
				Cell cell;

				cell = row.createCell(0);
				cell.setCellValue(i + 1);
				cell.setCellType(CellType.NUMERIC);
				cell = row.createCell(1);
				cell.setCellValue(result.get(i).activityId);
				cell.setCellType(CellType.NUMERIC);
				cell = row.createCell(2);
				cell.setCellValue(result.get(i).activityTitle);
				cell.setCellType(CellType.STRING);
				cell = row.createCell(3);
				cell.setCellValue(result.get(i).customerNumber);
				cell.setCellType(CellType.STRING);
				cell = row.createCell(4);
				cell.setCellValue(result.get(i).couponCode);
				cell.setCellType(CellType.STRING);
				cell = row.createCell(5);
				cell.setCellValue(result.get(i).secretCode);
				cell.setCellType(CellType.STRING);
				cell = row.createCell(6);
				cell.setCellValue(result.get(i).obtainTime.atZone(ZoneOffset.ofHours(8)).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
				cell.setCellType(CellType.STRING);
				cell = row.createCell(7);
				cell.setCellValue(result.get(i).effectiveTime.format(DateTimeFormatter.ISO_DATE));
				cell.setCellType(CellType.STRING);
				cell = row.createCell(8);
				cell.setCellValue(result.get(i).expirationTime.format(DateTimeFormatter.ISO_DATE));
				cell.setCellType(CellType.STRING);
			}

			sheet.autoSizeColumn(0);
			sheet.autoSizeColumn(1);
			sheet.autoSizeColumn(2);
			sheet.autoSizeColumn(3);
			sheet.autoSizeColumn(4);
			sheet.autoSizeColumn(5);
			sheet.autoSizeColumn(6);

			workbook.write(out);
		}
	}

	private List<CouponListItemVo> obtainmentSearchForExport(CouponListItemVo coupon) {
		var entity = obtainmentBuildEntityFromVo(coupon);
		var matcher = ExampleMatcher.matchingAll().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING)
				.withIgnorePaths(ignorePaths());
		var example = Example.of(entity, matcher);

		return obtainmentRepository.findAll(example).stream().map(this::obtainmentConvertEntityToVo).collect(Collectors.toList());
	}

}
